package jvm

import (
	"encoding/hex"
	"fmt"

	"github.com/spf13/cobra"
	clicomm "hundsun.com/hsl/hschain/cmd/cli/common"
	"hundsun.com/hsl/hschain/cmd/cli/dapp"
	"hundsun.com/hsl/hschain/common/util"
	"hundsun.com/hsl/hschain/contract/jvm/types"
	"hundsun.com/hsl/hschain/protos/common"
	"hundsun.com/hsl/hschain/protos/ledger"
	protosrpc "hundsun.com/hsl/hschain/protos/rpc"
	service "hundsun.com/hsl/hschain/rpc/impl"
)

var (
	flagContractName    = "contractName"
	flagContractAddr    = "contractAddr"
	flagContractVersion = "contractVersion"
	flagEventName       = "eventName"
)

// Cmd jvm命令行
func Cmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "jvm",
		Short: "Jvm operation",
		Args:  cobra.MinimumNArgs(1),
	}

	cmd.AddCommand(
		deployContractCmd(),
		callContractCmd(),
		getContractStatusCmd(),
		getHistoryContractEventCmd(),
		contractPureCmd(),
	)

	return cmd
}

// deployContractCmd ...
func deployContractCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "deploy",
		Short: "deploy jvm contract",
		RunE:  deployContract,
	}

	addDeployContractFlag(cmd)
	return cmd
}

func addDeployContractFlag(cmd *cobra.Command) {
	cmd.Flags().StringP(flagContractName, "n", "", "contract name")
	cmd.Flags().StringP(flagContractVersion, "v", "1.0.0", "contract version")

	opts := []clicomm.CmdOption{
		clicomm.WithRequiredFlag(clicomm.FlagPayload),
		clicomm.WithRequiredFlag(flagContractName),
		clicomm.WithRequiredFlag(flagContractVersion),
		clicomm.WithHiddenFlag(clicomm.FlagTo),
		clicomm.WithHiddenFlag(clicomm.FlagExecTy),
	}
	clicomm.AddTransactionPublicFlag(cmd, opts...)
}

func deployContract(cmd *cobra.Command, args []string) error {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println(err)
		}
	}()

	contractName, _ := cmd.Flags().GetString(flagContractName)
	contractVersion, _ := cmd.Flags().GetString(flagContractVersion)
	payload, _ := cmd.Flags().GetBytesHex(clicomm.FlagPayload)

	var contractID = &common.ContractIdentifier{}
	contractID.Name = contractName
	contractID.Version = contractVersion

	tx, err := clicomm.BuildTxWithCmd(cmd, types.JvmName, contractID, payload)
	if err != nil {
		return err
	}

	rpcLaddr, _ := cmd.Flags().GetString(clicomm.FlagRPCLaddr)
	clicomm.RPCCall(rpcLaddr, service.ReceiveTransaction, tx, func(result interface{}) {
		log.Printf("transaction '%s' sent successfully", hex.EncodeToString(tx.Hash()))
	})
	return nil
}

// callContractCmd ...
func callContractCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "call",
		Short: "call jvm contract",
		RunE:  callContract,
	}

	addCallContractFlag(cmd)
	return cmd
}

func addCallContractFlag(cmd *cobra.Command) {
	cmd.Flags().StringP(flagContractName, "n", "", "contract name")
	cmd.Flags().StringP(flagContractVersion, "v", "1.0.0", "contract version")

	opts := []clicomm.CmdOption{
		clicomm.WithRequiredFlag(clicomm.FlagPayload),
		clicomm.WithRequiredFlag(clicomm.FlagTo),
		clicomm.WithHiddenFlag(clicomm.FlagExecTy),
		clicomm.WithRequiredFlag(flagContractName),
		clicomm.WithRequiredFlag(flagContractVersion),
	}

	clicomm.AddTransactionPublicFlag(cmd, opts...)
}

func callContract(cmd *cobra.Command, args []string) error {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println(err)
		}
	}()

	contractName, _ := cmd.Flags().GetString(flagContractName)
	contractVersion, _ := cmd.Flags().GetString(flagContractVersion)
	payload, _ := cmd.Flags().GetBytesHex(clicomm.FlagPayload)

	var contractID = &common.ContractIdentifier{}
	contractID.Name = contractName
	contractID.Version = contractVersion

	tx, err := clicomm.BuildTxWithCmd(cmd, types.JvmName, contractID, payload)
	if err != nil {
		return err
	}

	rpcLaddr, _ := cmd.Flags().GetString(clicomm.FlagRPCLaddr)
	clicomm.RPCCall(rpcLaddr, service.ReceiveTransaction, tx, func(result interface{}) {
		log.Printf("transaction '%s' sent successfully", hex.EncodeToString(tx.Hash()))
	})
	return nil
}

// getContractStatusCmd 获取合约状态
func getContractStatusCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "version",
		Short: "Get contract version",
		Run:   contractStatus,
	}
	cmd.Flags().StringP(flagContractName, "n", "", "contract name")
	cmd.MarkFlagRequired(flagContractName)
	return cmd
}

func contractStatus(cmd *cobra.Command, args []string) {
	contractName, _ := cmd.Flags().GetString(flagContractName)
	rpcLaddr, _ := cmd.Flags().GetString(clicomm.FlagRPCLaddr)

	req := &common.QueryContract{
		Driver:   types.JvmName,
		FuncName: types.QueryContractVersionMethod,
		Param:    []byte(contractName),
	}

	clicomm.RPCCall(rpcLaddr, service.QueryContract, req, func(res interface{}) {
		obj := res.(service.JSONRPCResp)
		val, _ := util.Bytes2Struct(&common.ContractVersion{}, obj.Payload)
		clicomm.PrintJSON(val)
	})
}

// getHistoryContractEventCmd 获取历史合约事件
func getHistoryContractEventCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "history",
		Short: "Get history contract event",
		Run:   historyContractEvent,
	}

	cmd.Flags().String(flagContractName, "", "contract name")
	cmd.MarkFlagRequired(flagContractName)
	cmd.Flags().StringP(flagContractAddr, "a", "", "contract address")
	cmd.MarkFlagRequired(flagContractAddr)
	cmd.Flags().StringP(flagEventName, "n", "", "event name")
	cmd.MarkFlagRequired(flagEventName)
	dapp.StartEndCmd(cmd)
	cmd.Flags().String("indexParamFirst", "", "index first")
	cmd.Flags().String("indexParamSecond", "", "index second")
	cmd.Flags().String("indexParamThird", "", "index third")
	return cmd
}

func historyContractEvent(cmd *cobra.Command, args []string) {
	contractName, _ := cmd.Flags().GetString(flagContractName)
	contractAddr, _ := cmd.Flags().GetString(flagContractAddr)
	name, _ := cmd.Flags().GetString(flagEventName)
	start := dapp.GetStart(cmd)
	end := dapp.GetEnd(cmd)
	indexParamFirst, _ := cmd.Flags().GetString("indexParamFirst")
	indexParamSecond, _ := cmd.Flags().GetString("indexParamSecond")
	indexParamThird, _ := cmd.Flags().GetString("indexParamThird")

	subInfo := &protosrpc.ContractEventSubInfo{
		ContractId: &common.ContractIdentifier{
			Name: contractName,
		},
		ContractAddress:    contractAddr,
		Name:               name,
		StartBlockNum:      start,
		EndBlockNum:        end,
		IndexedParamFirst:  []byte(indexParamFirst),
		IndexedParamSecond: []byte(indexParamSecond),
		IndexedParamThird:  []byte(indexParamThird),
	}

	req, err := clicomm.CreateQueryContractReq(types.JvmName, types.QueryHistoryEventMethod, subInfo)
	if err != nil {
		fmt.Printf("create query contract request failed, err: %s", err.Error())
		return
	}

	rpcLaddr, _ := cmd.Flags().GetString(clicomm.FlagRPCLaddr)
	clicomm.RPCCall(rpcLaddr, service.QueryContract, req, func(res interface{}) {
		obj := res.(service.JSONRPCResp)
		val, _ := util.Bytes2Struct(&protosrpc.ContractEvents{}, obj.Payload)
		var arr []*clicomm.ContractEvent
		for _, item := range val.(*protosrpc.ContractEvents).Results {
			arr = append(arr, clicomm.FromPbContractEvent(item))
		}
		clicomm.PrintJSON(arr)
	})
}

// contractPureCmd 合约pure查询方法
func contractPureCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "pure",
		Short: "contract pure",
		RunE:  contractPure,
	}

	addPureContractFlag(cmd)
	return cmd
}

func addPureContractFlag(cmd *cobra.Command) {
	cmd.Flags().StringP(flagContractName, "n", "", "contract name")
	cmd.Flags().StringP(flagContractVersion, "v", "", "contract version")

	opts := []clicomm.CmdOption{
		clicomm.WithRequiredFlag(clicomm.FlagPayload),
		clicomm.WithRequiredFlag(flagContractName),
		clicomm.WithRequiredFlag(flagContractVersion),
	}

	clicomm.AddTransactionPublicFlag(cmd, opts...)
}

func contractPure(cmd *cobra.Command, args []string) error {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println(err)
		}
	}()
	contractName, _ := cmd.Flags().GetString(flagContractName)
	contractVersion, _ := cmd.Flags().GetString(flagContractVersion)
	payload, _ := cmd.Flags().GetBytesHex(clicomm.FlagPayload)

	fromAccount, err := clicomm.GetFromAccount(cmd)
	if err != nil {
		return fmt.Errorf("load account from keystore file failed, err: %s", err.Error())
	}

	pureTx := &ledger.Transaction{
		ContractId: &common.ContractIdentifier{Name: contractName, Version: contractVersion},
		Payload:    payload,
		From:       string(fromAccount.GetAddress()),
		To:         contractName,
	}
	pureTx.Sign(fromAccount.GetKeyPair())

	req, err := clicomm.CreateQueryContractReq(types.JvmName, types.QueryContractPureMethod, pureTx)
	if err != nil {
		fmt.Printf("create query contract request failed, err: %s", err.Error())
		return err
	}

	rpcLaddr, _ := cmd.Flags().GetString(clicomm.FlagRPCLaddr)
	clicomm.RPCCall(rpcLaddr, service.QueryContract, req, func(res interface{}) {
		obj := res.(service.JSONRPCResp)
		val, _ := util.Bytes2Struct(&ledger.TransactionReceipt{}, obj.Payload)
		clicomm.PrintJSON(clicomm.FromPbTxReceipt(val.(*ledger.TransactionReceipt)))
	})
	return nil
}
